001 /*
002 * Copyright 2005 Stephen J. McConnell.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
013 * implied.
014 *
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019 package net.dpml.state;
020
021 import java.io.Serializable;
022 import java.util.Arrays;
023
024 /**
025 * Default implementation of an application state descriptor.
026 *
027 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
028 * @version 1.0.2
029 */
030 public class DefaultState implements State, Serializable
031 {
032 private final String m_name;
033 private final Transition[] m_transitions;
034 private final Operation[] m_operations;
035 private final Interface[] m_interfaces;
036 private final State[] m_states;
037 private final Trigger[] m_triggers;
038 private final boolean m_terminal;
039
040 private transient State m_parent;
041
042 /**
043 * Creation of a new state.
044 */
045 public DefaultState()
046 {
047 this( "root" );
048 }
049
050 /**
051 * Creation of a new state.
052 * @param name the state name
053 */
054 public DefaultState( final String name )
055 {
056 this( name, new Trigger[0], new Transition[0], new Interface[0], new Operation[0], new State[0], true );
057 }
058
059 /**
060 * Creation of a new non-terminal state.
061 * @param name the state name
062 * @param triggers an array of triggers
063 * @param transitions an array of state transitions
064 * @param interfaces an array of management interface declarations
065 * @param operations an array of operations
066 * @param states an array of substates
067 */
068 public DefaultState(
069 final String name, final Trigger[] triggers, final Transition[] transitions,
070 final Interface[] interfaces, final Operation[] operations, final State[] states )
071 {
072 this( name, triggers, transitions, interfaces, operations, states, false );
073 }
074
075 /**
076 * Creation of a new state.
077 * @param name the state name
078 * @param triggers an array of triggers
079 * @param transitions an array of state transitions
080 * @param interfaces an array of management interface declarations
081 * @param operations an array of operations
082 * @param states an array of substates
083 * @param terminal the terminal flag
084 */
085 public DefaultState(
086 final String name, final Trigger[] triggers, final Transition[] transitions,
087 final Interface[] interfaces, final Operation[] operations, final State[] states, boolean terminal )
088 {
089 if( null == name )
090 {
091 throw new NullPointerException( "name" );
092 }
093
094 for( int i=0; i<operations.length; i++ )
095 {
096 Operation operation = operations[i];
097 if( null == operation )
098 {
099 throw new NullPointerException( "operation/" + i );
100 }
101 }
102
103 m_name = name;
104 m_triggers = triggers;
105 m_transitions = transitions;
106 m_operations = operations;
107 m_interfaces = interfaces;
108 m_states = states;
109 m_terminal = terminal;
110
111 for( int i=0; i<transitions.length; i++ )
112 {
113 Transition transition = transitions[i];
114 if( null == transition )
115 {
116 throw new NullPointerException( "transition/" + i );
117 }
118 transition.setState( this );
119 }
120
121 for( int i=0; i<triggers.length; i++ )
122 {
123 Trigger trigger = triggers[i];
124 if( null == trigger )
125 {
126 throw new NullPointerException( "trigger/" + i );
127 }
128 Action action = trigger.getAction();
129 if( action instanceof Transition )
130 {
131 Transition transition = (Transition) action;
132 transition.setState( this );
133 }
134 }
135
136 for( int i=0; i<interfaces.length; i++ )
137 {
138 Interface description = interfaces[i];
139 if( null == description )
140 {
141 throw new NullPointerException( "interface/" + i );
142 }
143 }
144
145 for( int i=0; i<states.length; i++ )
146 {
147 State state = states[i];
148 if( null == state )
149 {
150 throw new NullPointerException( "state/ " + i );
151 }
152 state.setParent( this );
153 }
154 }
155
156 /**
157 * Return the parent state to this state or null if this is the root of a
158 * state graph.
159 * @return the parent state
160 */
161 public State getParent()
162 {
163 return m_parent;
164 }
165
166 /**
167 * Set the parent state.
168 * @param state the parent state
169 */
170 public void setParent( State state )
171 {
172 if( null == m_parent )
173 {
174 m_parent = state;
175 }
176 else
177 {
178 final String error =
179 "Illegal attempt to reassign parent.";
180 throw new IllegalStateException( error );
181 }
182 }
183
184 /**
185 * Return the name of the state.
186 * @return the state name
187 */
188 public String getName()
189 {
190 return m_name;
191 }
192
193 /**
194 * Return the array of triggers associated with the state.
195 * @return the trigger array
196 */
197 public Trigger[] getTriggers()
198 {
199 return m_triggers;
200 }
201
202 /**
203 * Return the state path. The path is composed of a sequence of states
204 * from the root to this state.
205 * @return the state path
206 */
207 public State[] getStatePath()
208 {
209 if( null == m_parent )
210 {
211 return new State[]{this};
212 }
213 else
214 {
215 State[] path = m_parent.getStatePath();
216 State[] result = new State[ path.length + 1 ];
217 System.arraycopy( path, 0, result, 0, path.length );
218 result[ path.length ] = this;
219 return result;
220 }
221 }
222
223 /**
224 * Return the substates within this state.
225 * @return the substate array
226 */
227 public State[] getStates()
228 {
229 return m_states;
230 }
231
232 /**
233 * Return the array of transtions associated with the state.
234 * @return the transition array
235 */
236 public Transition[] getTransitions()
237 {
238 return m_transitions;
239 }
240
241 /**
242 * Return the array of operations associated with the state.
243 * @return the operation array
244 */
245 public Operation[] getOperations()
246 {
247 return m_operations;
248 }
249
250 /**
251 * Return the array of management interfaces associated with the state.
252 * @return the interfaces array
253 */
254 public Interface[] getInterfaces()
255 {
256 return m_interfaces;
257 }
258
259 /**
260 * Return the terminal flag.
261 * @return true if terminal
262 */
263 public boolean getTerminal()
264 {
265 return isTerminal();
266 }
267
268 /**
269 * Test is the state is a terminal state.
270 * @return true if terminal
271 */
272 public boolean isTerminal()
273 {
274 return m_terminal;
275 }
276
277 /**
278 * Return a string representation of the instance.
279 * @return the string value
280 */
281 public String toString()
282 {
283 StringBuffer buffer = new StringBuffer();
284 State[] path = getStatePath();
285 for( int i=0; i<path.length; i++ )
286 {
287 State state = path[i];
288 if( i>0 )
289 {
290 buffer.append( "/" );
291 }
292 buffer.append( state.getName() );
293 }
294 return buffer.toString();
295 //return "[" + m_name + "]";
296 }
297
298 /**
299 * Compare this object to another for equality.
300 * @param other the other object
301 * @return true if the object is equal to this object
302 */
303 public boolean equals( Object other )
304 {
305 if( null == other )
306 {
307 return false;
308 }
309 else if( other instanceof DefaultState )
310 {
311 DefaultState state = (DefaultState) other;
312 if( !m_name.equals( state.getName() ) )
313 {
314 return false;
315 }
316 else if( m_terminal != state.isTerminal() )
317 {
318 return false;
319 }
320 else if( !Arrays.equals( m_triggers, state.getTriggers() ) )
321 {
322 return false;
323 }
324 else if( !Arrays.equals( m_transitions, state.getTransitions() ) )
325 {
326 return false;
327 }
328 else if( !Arrays.equals( m_operations, state.getOperations() ) )
329 {
330 return false;
331 }
332 else
333 {
334 return Arrays.equals( m_states, state.getStates() );
335 }
336 }
337 else
338 {
339 return false;
340 }
341 }
342
343 /**
344 * Compute the hashcode for this instance.
345 * @return the hashcode value
346 */
347 public int hashCode()
348 {
349 if( null == m_parent )
350 {
351 return m_name.hashCode();
352 }
353 else
354 {
355 int hash = m_parent.hashCode();
356 hash ^= m_name.hashCode();
357 return hash;
358 }
359 }
360
361 private boolean equals( Object a, Object b )
362 {
363 if( null == a )
364 {
365 return null == b;
366 }
367 else
368 {
369 return a.equals( b );
370 }
371 }
372 }